"""
脚本任务处理模块
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))

from typing import List, Dict, Any
from com.db.script_task_manager import ScriptTaskManager
from com.db.task_manager import TaskManager
from com.db.script_scan_account_manager import ScriptScanAccountManager
from com.db.order_account_manager import OrderAccountManager
from com.db.script_manager import ScriptManager
from com.db.script_scan_account_manager import ScriptScanAccountManager
from com.scan_account_pool import ScanAccountPool
from reqapi.get_csgo_page_list2 import GetCsgoPagedListV2Api
from reqapi.get_commodity_list import GetCommodityListApi
from biz.scan_commodity import CommodityScanner
import time
import threading
import queue
import json
import atexit
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from PyQt6.QtCore import QObject, pyqtSignal, Qt
from com.utils.logger import get_logger,MODULE_SCAN
import random
from com.constants.wear_ranges import WearCategory, WEAR_CATEGORY_NAMES
from enum import Enum

# 创建中文名称到WearCategory的反向映射
WEAR_CATEGORY_BY_NAME = {name: category for category, name in WEAR_CATEGORY_NAMES.items()}

# 扫货模式枚举
class ScanMode(Enum):
    DYNAMIC = "dynamic"  # 动态扫货
    POLLING = "polling"  # 轮询扫货

class ScriptTaskProcessor(QObject):
    """脚本任务处理器，使用单例模式"""
    
    _instance = None
    _instance_lock = threading.Lock()
    
    # 日志信号
    log_signal = pyqtSignal(str)
    
    def __new__(cls):
        print("开始创建 ScriptTaskProcessor 实例...")
        if cls._instance is None:
            print("没有已存在的实例，准备创建新实例")
            with cls._instance_lock:
                if cls._instance is None:
                    print("在锁内确认没有已存在的实例，开始创建")
                    # 确保在主线程中创建实例
                    if threading.current_thread() is not threading.main_thread():
                        print("错误：不在主线程中创建实例")
                        raise RuntimeError("ScriptTaskProcessor must be created in the main thread")
                    
                    print("创建实例并初始化 QObject")
                    # 创建实例并初始化 QObject
                    cls._instance = super().__new__(cls)
                    print("实例创建成功，准备初始化 QObject")
                    try:
                        QObject.__init__(cls._instance)
                        print("QObject 初始化成功")
                    except Exception as e:
                        import traceback
                        print(f"QObject 初始化失败: {str(e)}\n{traceback.format_exc()}")
                        raise
                    
                    print("开始初始化其他属性")
                    try:
                        # 初始化其他属性
                        print("初始化 script_task_manager")
                        cls._instance.script_task_manager = ScriptTaskManager()
                        
                        print("初始化 task_manager")
                        cls._instance.task_manager = TaskManager()
                        
                        print("初始化 script_scan_account_manager")
                        cls._instance.script_scan_account_manager = ScriptScanAccountManager()
                        
                        print("初始化 order_account_manager")
                        cls._instance.order_account_manager = OrderAccountManager()
                        
                        print("初始化 script_manager")
                        cls._instance.script_manager = ScriptManager()
                        
                        print("准备创建 CommodityScanner 实例...")
                        try:
                            cls._instance.scanner = CommodityScanner(
                                order_account=None,
                                task=None,
                                script=None
                            )
                            print("CommodityScanner 创建成功")
                        except Exception as e:
                            import traceback
                            print(f"CommodityScanner 创建失败: {str(e)}\n{traceback.format_exc()}")
                            cls._instance.scanner = None
                            print("设置 scanner 为 None")
                        
                        print("初始化基本属性")
                        cls._instance._stop_event = threading.Event()
                        cls._instance._processing_thread = None
                        cls._instance.scan_mode = ScanMode.DYNAMIC  # 默认使用动态扫货模式
                        print("基本属性初始化成功")
                    except Exception as e:
                        import traceback
                        print(f"初始化属性失败: {str(e)}\n{traceback.format_exc()}")
                        raise
                    
                    print("注册退出处理函数")
                    try:
                        atexit.register(cls._instance.cleanup)
                        print("退出处理函数注册成功")
                    except Exception as e:
                        import traceback
                        print(f"注册退出处理函数失败: {str(e)}\n{traceback.format_exc()}")
                    
                    print("准备连接扫描器的日志信号")
                    # 连接扫描器的日志信号
                    if cls._instance.scanner:
                        try:
                            print("开始连接信号...")
                            cls._instance.scanner.log_signal.connect(
                                cls._instance.log_signal.emit,
                                Qt.ConnectionType.QueuedConnection
                            )
                            print("信号连接成功")
                        except Exception as e:
                            import traceback
                            print(f"连接信号失败: {str(e)}\n{traceback.format_exc()}")
                    else:
                        print("扫描器为 None，无法连接信号")
                    
                    print("ScriptTaskProcessor 初始化完成")
        
        return cls._instance
    
    def __init__(self):
        """由于使用单例模式，这里不需要任何初始化"""
        self.logger = get_logger(module=MODULE_SCAN)
        self.executor = ThreadPoolExecutor(max_workers=5)  # 用于并行处理账号请求
    
    def log(self, message: str):
        """发送日志消息"""
        try:
            self.log_signal.emit(message)
            self.logger.info(message)
        except:
            self.logger.info(message)
        
    def cleanup(self):
        """清理资源"""
        try:
            if self._processing_thread and self._processing_thread.is_alive():
                self.stop()
        except Exception as e:
            self.logger.info(f"清理资源时发生错误: {str(e)}")
        
    def start(self, script_id: int, interval: int = 1, scan_mode: ScanMode = ScanMode.DYNAMIC):
        """
        开始处理脚本任务
        
        Args:
            script_id: 脚本ID
            interval: 任务处理间隔（秒）
            scan_mode: 扫货模式，默认为动态扫货
        """
        if self._processing_thread and self._processing_thread.is_alive():
            self.log("已有任务正在处理中")
            return
            
        # 设置扫货模式
        self.scan_mode = scan_mode
        self.log(f"使用扫货模式: {scan_mode.value}")
            
        # 获取脚本信息
        script = self.script_manager.get_script_by_id(script_id)
        if not script:
            self.log(f"找不到脚本 {script_id}")
            return
            
        # 获取脚本关联的下单账号
        order_account = None
        if script.get('order_account_id'):
            order_account = self.order_account_manager.get_account_by_id(script['order_account_id'])
            if order_account:
                self.log(f"使用脚本关联的下单账号: {order_account.get('name', order_account['id'])}")
            else:
                self.log(f"找不到脚本关联的下单账号: {script['order_account_id']}")
            
        # 更新scanner的script和order_account信息
        self.scanner = CommodityScanner(
            script=script,
            order_account=order_account
        )
        
        # 启动处理线程
        self._stop_event.clear()
        self._processing_thread = threading.Thread(
            target=self._process_loop,
            args=(script_id, interval),
            daemon=True
        )
        self._processing_thread.start()
        self.log(f"开始处理脚本 {script_id} 的任务，处理间隔 {interval} 秒")
        
    def stop(self):
        """停止处理脚本任务"""
        if not self._processing_thread or not self._processing_thread.is_alive():
            self.log("任务处理器未在运行")
            return
            
        self.log("正在停止任务处理器...")
        self._stop_event.set()
        
        # 设置超时时间，避免无限等待
        timeout = 5  # 5秒超时
        start_time = time.time()
        while self._processing_thread.is_alive():
            if time.time() - start_time > timeout:
                self.log("停止任务处理器超时")
                break
            time.sleep(0.1)
            
        self.log("任务处理器已停止")
        
    def _process_loop(self, script_id: int, interval: int):
        """
        任务处理循环
        
        Args:
            script_id: 脚本ID
            interval: 任务处理间隔（秒）
        """
        while not self._stop_event.is_set():
            try:
                self.process_script_tasks(script_id)
            except Exception as e:
                self.log(f"处理任务时发生错误: {str(e)}")
                time.sleep(1)  # 出错后等待一秒再继续
                continue
                
            # 等待指定时间或者直到收到停止信号
            wait_until = time.time() + interval
            while time.time() < wait_until and not self._stop_event.is_set():
                time.sleep(0.1)  # 使用更小的检查间隔
                
    def _process_account_tasks(self, account, tasks_dict, max_buy_price, script_id):
        """
        处理单个账号的任务
        
        Args:
            account: 账号信息
            tasks_dict: 任务字典
            max_buy_price: 最大购买价格
            script_id: 脚本ID
        """
        try:
            if self._stop_event.is_set():
                return
                
            # 添加随机延迟（0-3秒）
            random_delay = random.uniform(0, 3)
            time.sleep(random_delay)
            
            # self.log(f"\n【使用账号】[{account.get('account_name', account['id'])}]")
            
            # 初始化API
            csgo_api = GetCsgoPagedListV2Api(
                device_token=account['device_id'],
                device_id=account['device_id'],
                auth_token=account['uu_token']
            )
            
            # self.log("【开始获取商品列表】")
            # 从任务列表中获取所有不重复的exterior值
            exteriors = set()
            for task in tasks_dict.values():
                if 'exterior' in task and task['exterior']:
                    exteriors.add(task['exterior'])
            
            # self.log(f"exterior值: {exteriors}")
            # 将exterior值转换为对应的WearCategory值
            wear_categories = []
            for exterior in exteriors:
                try:
                    # 通过中文名称查找对应的WearCategory
                    category = WEAR_CATEGORY_BY_NAME.get(exterior)
                    if category:
                        wear_categories.append(category.value)
                    else:
                        self.logger.warning(f"未找到对应的WearCategory: {exterior}")
                except (KeyError, ValueError) as e:
                    self.logger.warning(f"转换exterior值时出错: {exterior}, 错误: {str(e)}")
            
            # 如果没有有效的exterior值，使用所有可能的值
            if not wear_categories:
                wear_categories = [cat.value for cat in WearCategory]
            
            filter_map = {
                "Exterior": wear_categories,
                "Type": [
                    "CSGO_Type_Pistol_Unlimited",
                    "CSGO_Type_Rifle_Unlimited",
                    "CSGO_Type_SMG_Unlimited",
                    "CSGO_Type_Shotgun_Unlimited",
                    "CSGO_Type_Machinegun_Unlimited"
                ]
            }
            
            # self.log(f"filter_map: {json.dumps(filter_map, ensure_ascii=False)}")
            
            api_result = csgo_api.execute(
                filter_map=filter_map,
                game_id=730,
                list_sort_type=0,
                list_type=10,
                min_price="0",
                max_price=str(max_buy_price),
                page_index=1,
                page_size=100,
                property_filter_tags=[],
                sort_type=0,
                sticker_abrade=0,
                stickers_is_sort=False,
                session_id=str(script_id)
            )
            
            if not api_result:
                self.log("【API请求失败】")
                return
            
            if api_result.get('Code') != 0:
                self.log(f"【API返回错误】{api_result.get('Msg')}")
                return
            
            commodity_list = api_result.get('Data', {}).get('categoryList', [])
            if not commodity_list:
                self.log("【商品列表为空】")
                return
                
            # self.log(f"【获取商品】{len(commodity_list)} 个")
            
            # 为每个商品创建扫描任务
            scan_tasks = []
            
            for commodity in commodity_list:
                if self._stop_event.is_set():
                    break
                    
                # 查找匹配的任务
                commodity_id = commodity.get('Id')
                if commodity_id is None:
                    self.log(f"商品数据缺少Id字段: {commodity}")
                    continue
                
                commodity_id_str = str(commodity_id)
                matching_task = tasks_dict.get(commodity_id_str)
                
                if matching_task:
                    # 解析 filter_conf JSON 字符串
                    filter_conf = {}
                    try:
                        filter_conf_str = matching_task.get('filter_conf')
                        if filter_conf_str:
                            filter_conf = json.loads(filter_conf_str)
                    except json.JSONDecodeError as e:
                        self.log(f"解析 filter_conf 失败: {str(e)}")
                        self.log(f"原始 filter_conf 字符串: {filter_conf_str}")
                    except Exception as e:
                        self.log(f"处理 filter_conf 时发生错误: {str(e)}")
                    
                    # 创建新的任务对象，包含所有必要的信息
                    scan_task = {
                        'goods_id': matching_task.get('goods_id'),
                        'buy_price_limit': matching_task.get('buy_price_limit'),
                        'filter_conf': filter_conf,
                        'task_id': matching_task.get('id'),
                        'commodity_data': commodity,
                        'task': matching_task
                    }
                    scan_tasks.append(scan_task)
            
            # 批量处理商品
            if scan_tasks and not self._stop_event.is_set():
                self.scanner.start_scan_tasks(scan_tasks)
            else:
                self.log("没有找到匹配的扫描任务")
                
        except Exception as e:
            self.log(f"处理账号任务时发生错误: {str(e)}")

    def process_script_tasks(self, script_id: int):
        """处理指定脚本的任务"""
        if self.scan_mode == ScanMode.DYNAMIC:
            self._process_dynamic_scan(script_id)
        else:  # ScanMode.POLLING
            self._process_polling_scan(script_id)
            
    def _process_dynamic_scan(self, script_id: int):
        """
        处理动态扫货模式的任务
        
        Args:
            script_id: 脚本ID
        """
        try:
            # 在单个事务中获取所有必要的数据
            session = self.script_scan_account_manager.db.get_session()
            try:
                # 获取脚本
                script = self.script_manager.get_script_by_id(script_id)
                if not script:
                    self.log(f"没有找到 ID 为 {script_id} 的脚本")
                    return
                    
                # 获取脚本关联的扫货账号
                scan_accounts = self.script_scan_account_manager.get_scan_accounts_by_script_id(script_id)
                if not scan_accounts:
                    self.log(f"脚本 {script_id} 没有关联的扫货账号")
                    return
                
                # 获取脚本关联的任务
                tasks = self.script_task_manager.get_by_script_id(script_id)
                if not tasks:
                    self.log(f"脚本 {script_id} 没有关联的任务")
                    return
                    
                # 将任务列表转换为字典
                tasks_dict = {}
                for task in tasks:
                    task_goods_id = task.get('goods_id')
                    if task_goods_id is None:
                        self.log(f"任务数据缺少goods_id字段: {task}")
                        continue
                    tasks_dict[str(task_goods_id)] = task
                
                # 计算最大购买价格
                max_buy_price = round(max(task['buy_price_limit'] for task in tasks if task.get('buy_price_limit') is not None), 2)
                # self.log(f"【价格限制】{max_buy_price}")
                
                # 并行处理每个账号的任务
                futures = []
                for account in scan_accounts:
                    if self._stop_event.is_set():
                        break
                    future = self.executor.submit(
                        self._process_account_tasks,
                        account,
                        tasks_dict,
                        max_buy_price,
                        script_id
                    )
                    futures.append(future)
                
                # 等待所有任务完成
                for future in as_completed(futures):
                    try:
                        future.result()
                    except Exception as e:
                        self.log(f"执行账号任务时发生错误: {str(e)}")
                
            finally:
                session.close()
                
        except Exception as e:
            self.log(f"处理脚本任务时发生错误: {str(e)}")
            
    def _process_polling_scan(self, script_id: int):
        """
        处理轮询扫货模式的任务
        
        Args:
            script_id: 脚本ID
        """
        try:
            # 检查是否是首次执行
            is_first_run = not hasattr(self, '_polling_futures') or not self._polling_futures
            
            # 如果不是首次执行，检查之前的任务是否有完成的
            if not is_first_run:
                completed_futures = []
                for future in self._polling_futures:
                    if future.done():
                        # 检查任务是否成功完成
                        try:
                            # 获取结果，但不使用，只是为了触发异常检查
                            future.result()
                            completed_futures.append(future)
                        except Exception as e:
                            # 任务执行失败，记录日志并将其标记为已完成
                            self.log(f"轮询扫货任务执行失败: {str(e)}")
                            completed_futures.append(future)
            
                # 如果没有完成的任务，直接返回
                if not completed_futures:
                    return
            
                # 从列表中移除已完成的任务
                for future in completed_futures:
                    self._polling_futures.remove(future)
                
                # self.log(f"有 {len(completed_futures)} 个轮询扫货任务完成，继续分配新任务")
            
            # 获取脚本关联的任务
            tasks = self.script_task_manager.get_by_script_id(script_id)
            if not tasks:
                self.log(f"脚本 {script_id} 没有关联的任务")
                return
            
            # self.log(f"轮询扫货模式: 获取到 {len(tasks)} 个任务")
            
            # 确定本次需要处理的任务数量
            if is_first_run:
                # 首次执行，获取可用的扫货账号数量
                account_pool = ScanAccountPool()
                # 尝试获取一定数量的账号，看能获取多少
                test_accounts = account_pool.get_accounts_with_intervals(min(10, len(tasks)))
                batch_size = len(test_accounts)
            
                if batch_size == 0:
                    self.log("没有可用的扫货账号")
                    return
                
                # 初始化任务索引记录
                self._last_task_index = 0
            else:
                # 后续执行，根据已完成的任务数量确定新任务数
                batch_size = len(completed_futures)
        
            # 如果有正在执行的任务，则跳过这些任务的商品ID
            processing_goods_ids = set()
            if hasattr(self, '_polling_futures') and self._polling_futures:
                for task in tasks:
                    goods_id = str(task.get('goods_id'))
                    if self.scanner._is_processing(goods_id):
                        processing_goods_ids.add(goods_id)
        
            # 从任务列表中选择未处理的任务，使用轮询方式确保所有任务都能被处理到
            unprocessed_tasks = []
            task_count = len(tasks)
        
            # 如果没有记录上次处理的索引，初始化为0
            if not hasattr(self, '_last_task_index'):
                self._last_task_index = 0
            
            # 从上次处理的位置开始，循环遍历任务列表
            checked_count = 0
            while len(unprocessed_tasks) < batch_size and checked_count < task_count:
                # 获取当前索引的任务
                current_index = (self._last_task_index + checked_count) % task_count
                task = tasks[current_index]
                checked_count += 1
            
                # 检查任务是否正在处理中
                goods_id = str(task.get('goods_id'))
                if goods_id not in processing_goods_ids:
                    unprocessed_tasks.append(task)
                
            # 更新上次处理的索引，为下一次做准备
            self._last_task_index = (self._last_task_index + checked_count) % task_count
        
            # 如果没有找到足够的未处理任务，减少批次大小
            batch_size = len(unprocessed_tasks)
        
            if batch_size == 0:
                self.log("没有找到未处理的任务")
                return
            
            # self.log(f"本次将处理 {batch_size} 个任务，下次从索引 {self._last_task_index} 开始")
        
            # 准备扫描任务
            scan_tasks = []
            for task in unprocessed_tasks:
                goods_id = str(task.get('goods_id'))
            
                # 解析过滤条件
                filter_conf = {}
                try:
                    filter_conf_str = task.get('filter_conf')
                    if filter_conf_str:
                        filter_conf = json.loads(filter_conf_str)
                except Exception as e:
                    self.log(f"处理 filter_conf 时发生错误: {str(e)}")
            
                # 创建扫描任务对象
                scan_task = {
                    'goods_id': goods_id,
                    'buy_price_limit': task.get('buy_price_limit'),
                    'filter_conf': filter_conf,
                    'task_id': task.get('id'),
                    'task': task,
                    'commodity_data': {
                        'Id': goods_id,
                        'TemplateId': goods_id,
                        'CommodityName': task.get('name', f'商品{goods_id}')
                    },
                    # 添加一个回调标志，表示这是轮询模式的任务
                    'polling_mode': True
                }
                scan_tasks.append(scan_task)
        
            if not scan_tasks:
                self.log("没有有效的扫描任务")
                return
            
            # 获取扫货账号
            account_pool = ScanAccountPool()
            needed_accounts = len(scan_tasks)
            accounts = account_pool.get_accounts_with_intervals(needed_accounts)
        
            if not accounts:
                self.log("没有可用的扫货账号")
                return
            
            # self.log(f"获取到 {len(accounts)} 个扫货账号")
        
            # 创建一个回调函数，在API调用完成后立即通知
            def api_callback(future):
                # 这个回调函数会在API调用完成后立即执行
                print("扫货API调用完成，立即返回通知")
                # 这里可以添加其他处理逻辑，如更新状态、记录日志等
        
            # 批量处理商品
            if scan_tasks and not self._stop_event.is_set():
                # 调用扫描接口并立即返回，不等待结果
                futures, _ = self.scanner.start_scan_tasks(scan_tasks)
            
                # 为每个future添加回调函数
                for future in futures:
                    future.add_done_callback(api_callback)
            
                # 保存futures以便后续检查
                if not hasattr(self, '_polling_futures'):
                    self._polling_futures = []
                self._polling_futures.extend(futures)
            
                # self.log(f"已分配 {len(scan_tasks)} 个轮询扫货任务，当前共有 {len(self._polling_futures)} 个任务在执行")
            else:
                self.log("没有找到匹配的扫描任务")
                
        except Exception as e:
            self.log(f"处理轮询扫货任务时发生错误: {str(e)}")
            import traceback
            self.log(traceback.format_exc())

def main():
    """主函数"""
    # 创建logger
    logger = get_logger(module=MODULE_SCAN)
    
    try:
        # 获取脚本ID
        
            
        script_id = 1
        processor = ScriptTaskProcessor()
        
        # 开始处理脚本任务
        processor.start(script_id)
        logger.info(f"开始处理脚本 {script_id} 的任务...")
        
        # 保持主线程运行
        while True:
            try:
                time.sleep(1)
            except KeyboardInterrupt:
                logger.info("\n收到停止信号，正在停止...")
                processor.stop()
                break
    except Exception as e:
        logger.error(f"运行出错: {str(e)}")
    finally:
        logger.info("程序结束")

def test_polling_scan():
    """测试轮询扫货模式"""
    # 创建logger
    logger = get_logger(module=MODULE_SCAN)
    
    try:
        # 获取脚本ID
        script_id = 1  # 替换为你要测试的脚本ID
        
        # 创建任务处理器
        processor = ScriptTaskProcessor()
        
        # 设置轮询间隔（秒）
        interval = 5  # 每5秒检查一次任务完成情况
        
        # 开始处理脚本任务，使用轮询扫货模式
        processor.start(script_id, interval=interval, scan_mode=ScanMode.POLLING)
        logger.info(f"开始使用轮询扫货模式处理脚本 {script_id} 的任务，轮询间隔: {interval}秒")
        
        # 保持主线程运行
        try:
            while True:
                # 显示当前状态
                if hasattr(processor, '_polling_futures'):
                    total_tasks = len(processor._polling_futures)
                    completed_tasks = sum(1 for f in processor._polling_futures if f.done())
                    logger.info(f"当前状态: 总任务数: {total_tasks}, 已完成: {completed_tasks}, 进行中: {total_tasks - completed_tasks}")
                
                time.sleep(interval)
        except KeyboardInterrupt:
            logger.info("\n收到停止信号，正在停止...")
            processor.stop()
    except Exception as e:
        logger.error(f"测试轮询扫货模式时出错: {str(e)}")
        import traceback
        logger.error(traceback.format_exc())
    finally:
        logger.info("轮询扫货测试结束")

if __name__ == "__main__":
    # 选择要运行的测试
    # main()  # 运行原始的动态扫货模式
    test_polling_scan()  # 运行轮询扫货模式测试
